package com.androidvizlab.bachelor.services;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;

import com.androidvizlab.bachelor.R;
import com.androidvizlab.bachelor.Sockets.DataPushMqttClient;
import com.androidvizlab.bachelor.activities.ViewResultDataActivity;
import com.androidvizlab.bachelor.notifications.NoticeManager;
import com.androidvizlab.bachelor.utilities.ConnectivityDetector;
import com.androidvizlab.bachelor.utilities.NumberConverter;
import com.androidvizlab.bachelor.utilities.WakeLockManager;
import com.ibm.mqtt.IMqttClient;
import com.ibm.mqtt.MqttException;
import com.ibm.mqtt.MqttSimpleCallback;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.speech.tts.TextToSpeech;
import android.util.Log;

/**
 * @author The Hive
 * A data push service using ibm's MQTT
 * This service handles a client which maintain a connection to a message broker.
 * A publisher class on the other end of the connection publishes data on a particular topic(s)
 * the client subscribes to a topic and waits for a publication of content.
 * the broker relays information to the publisher and subscriber.
 */
public class VizLabDataMQTTPushService extends Service implements MqttSimpleCallback{

	//Service Status
	public String STATUS_MSG = "";
	
	private HashMap<String,String> statusMap = new HashMap<String,String>();
	
	//BROKER CONNECTION VARIABLES
	private String brokerHostName = "";
	private int brokerPortNumber = 1883;
	
	//KEEP ALIVE
	//The uses this to keep the connection alive.
	private short keepalive = 20 * 60; //in seconds
	
	//TOPICs
	private String[] topics = null;
	private String topicTitle = "new_available_data";
	
	//MQTT CLIENT
	private String MQTT_CLIENT_ID = null; //mqtt client id
	
	private IMqttClient mqttClient = null; //mqtt client to connect to the broker 
	
	//CREATES NOTIFICATION WHEN A MESSAGE FROM THE BROKER/SERVER IS RECEIVED
	private NoticeManager noticemanager = null;
	private NoticeManager noticemanagerongoing = null;
	
	//CONNECTION DETECTOR(WIFI / MOBILE)
	private ConnectivityDetector connectivityDetector = null;
	
	//WAKELOCK AND POWER MANAGEMENT
	private WakeLockManager wakelockmanager = new WakeLockManager();
	private final String wakelockTag = "VizLabMqttDataPush wakeup";
	
	//ACTIONS
	public static final String BROKER_PING = "com.androidvizlab.mqtt.PING";
	
	//THREADS and RUNNABLES
	private Thread t = null;
	
	//Data push broker client
	private DataPushMqttClient subscriberclient = null;
	
	//Voice alert
	private boolean isVoiceAlertActive = false;
	private boolean ttsStatusOK = false;
	private TextToSpeech tts = null;
	
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}
	
	@Override
	public void onCreate()
	{
		super.onCreate();
		loadPreferences(); //fetches connection settings from shared preferences
		populateStatusMap(); //collection of status the service can have.
		initComponents();
	}
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startId)
	{
		//setTopicName(intent); //get the topic name to subscribe to.
		
		//Notify user via status bar that the service is getting ready..
		noticemanager.createNotification("INITIALISING", statusMap.get("INITIALISING"), NoticeManager.NOTIFICATION_AUTOCANCEL_CLEAR);
		
		noticemanagerongoing.createNotification("RUNNING", statusMap.get("RUNNING"), NoticeManager.NOTIFICATION_ONGOING);
		
		subscriberclient = new DataPushMqttClient(MQTT_CLIENT_ID, this, noticemanager);
		//subscriberclient.setUpMqttClientAndCallback();
		
		subscriberclient.setBrokerHostName(brokerHostName);
		subscriberclient.setBrokerPortNumber(brokerPortNumber);
		
		start(); //Start the client 
		
		return START_STICKY;
	}
	
	@Override
	public void onDestroy()
	{
		super.onDestroy();
		
		//shutdown TextToSpeech
		closeTextToSpeech();
		
		//disconnect form message broker
		subscriberclient.disconnectFromMqttBroker();
		
		noticemanager.clearAll(); //clears all notification raise by the app
		
		stop();
		
		//unregister receivers
		//todo
	}
	
	/**
	 * Initialise components
	 */
	public void initComponents()
	{
		connectivityDetector = new ConnectivityDetector(getApplicationContext());
		noticemanager = new NoticeManager(getApplicationContext(),NoticeManager.PREDEFINE_NOTIFICATION_ID2, NoticeManager.PREDEFINED_NOTIFICATION_TAG2, R.drawable.vizlabnotificationicon);
		noticemanagerongoing = new NoticeManager(getApplicationContext(), NoticeManager.PREDEFINED_NOTIFICATION_ID0,
				NoticeManager.PREDEFINED_NOTIFICATION_TAG0,R.drawable.vizlabnotificationicon);
	}
	
	//This method fetches information from the sharedpreferences
	//sets broker address and other info.
	/**
	 * Get saved preferences and settings from teh SharePreferences.
	 */
	private void loadPreferences()
	{
		SharedPreferences preference = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
		brokerHostName = preference.getString("brokeraddress", "");
		brokerPortNumber = NumberConverter.convertToInt(preference.getString("brokerPortNumber", "1883"), 1883);
		isVoiceAlertActive = preference.getBoolean("voicealert", false);
	}
	
	/**
	 * Sets the topic to subscribe to
	 * @param intent
	 */
	private void setTopicName(Intent intent)
	{
		//Bundle bundle = intent.getExtras();
		this.topicTitle = intent.getStringExtra("topic");
		
		//List of topics
		topics = new String[1];
		topics[0] = topicTitle;
	}
	
	//*** TEXT TO SPEECH ***//
	
	/**
	 * Initialises TextToSpeech function and test for errors
	 * If the initialisation fails default sound and vibrate will be used
	 */
	private void initTextToSpeech()
	{
		if(isVoiceAlertActive == true){
			tts = new TextToSpeech(this.getApplicationContext(), new TextToSpeech.OnInitListener(){
	
				@Override
				public void onInit(int status) 
				{
					if(status != TextToSpeech.ERROR)
					{
						int result = tts.setLanguage(Locale.ENGLISH);
						
						if(result == TextToSpeech.LANG_MISSING_DATA || 
								result == TextToSpeech.LANG_NOT_SUPPORTED)
						{
							ttsStatusOK = false;
						}
						else
						{
							ttsStatusOK = true;
							Log.i("tts ok","should work properly");
						}
					}
				}
				
			});
		}
	}
	
	/**
	 * Sound an alert to the user.
	 * 
	 * @param msg message to speak
	 */
	public void speakAlert(String msg)
	{
		if(tts != null && ttsStatusOK == true)
		{
			tts.speak(msg,TextToSpeech.QUEUE_FLUSH, null);
			Log.i("tts ok","Why you no speak");
		}
	}
	
	/**
	 * Shutdown the TexToSpeech function
	 */
	private void closeTextToSpeech()
	{
		if(tts != null)
		{
			tts.stop();
			tts.shutdown();
		}
	}
	
	//****** INITIALISE CLIENT AND START CONNECTION *****//
	/**
	 * starts the MQTT clients thread.
	 */
	public void start()
	{	
		//start
		t = new Thread(subscriberclient,"VizLabDataPushService");
		
		t.start();
		
		//register broadcast receiver
	}
	
	/**
	 * stop the MQTT clients thread
	 */
	public void stop()
	{
		if(t != null)
		{
			try 
			{
				t.join();
				t = null;
			} 
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			
		}
	}
	
	//**** PING BROKER ****//
	
	/**Uses an AlarmManager to schedule a ping to the broker
	*This method insures that the cpu is woken up atleast once
	*before the next keep alive is sent, keep the connection to the broker alive.
	*This is use in conjunction with a broadcastreceiver to send a ping to the mqtt broker
	*/
	public void scheduleNextBrokerPing()
	{
		Intent intent = new Intent(BROKER_PING);
		PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
		
		Calendar wakeuptime = Calendar.getInstance();
		wakeuptime.add(Calendar.SECOND, keepalive);
		
		AlarmManager alarmmanager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
		alarmmanager.set(AlarmManager.RTC_WAKEUP, wakeuptime.getTimeInMillis(), pendingIntent);
	}
	
	//*** BROADCAST RECEIVER FOR PINGING THE BROKER ***//
	
	/**
	 * This is used to handle the keep-alive protocol of the service.
	 * A PING message is sent to the mqtt broker when a broadcast is receive from the
	 * alarmanager that was scheduled on the method VizLabDataMQTTPushService.scheduleNextBrokerPing()
	 * After the ping message is sent, we schedule the next ping.
	 * 
	 * @author TheHive
	 */
	private class PingHandler extends BroadcastReceiver
	{

		@Override
		public void onReceive(Context context, Intent intent) {
			
			try
			{
				subscriberclient.sendPINGMsgToBroker();
			}
			catch(MqttException e)
			{
				//notify user that something went wrong
				noticemanager.createNotification("PING ERROR", "Something went wrong. Disconnecting..", NoticeManager.NOTIFICATION_AUTOCANCEL);
				//disconnect the restablish connection to the broker
				subscriberclient.disconnectFromMqttBroker();
				
				if(subscriberclient.connectToMqttBroker())
				{
					subscriberclient.subscribe(topicTitle);
				}
			}
			scheduleNextBrokerPing(); //schedule a new ping
		}
		
	}
	
	//**** NETWORK STATUS CHANGE RECEIVER *****//
	
	/**Detects network availability.
	*This allows us to wait for network availability after losing a connection,
	*The service then reconnects to the broker and (re)subscribe to the topics.
	* @author TheHive
	*/ 
	private class NetworkAvailabilityReceiver extends BroadcastReceiver
	{
		@Override
		public void onReceive(Context context, Intent intent) {	
			
			WakeLockManager.acquire(getApplicationContext(),wakelockTag);
			
			if(connectivityDetector.isConnectedToNetworkOnline())
			{
				if(subscriberclient.connectToMqttBroker())
				{
					subscriberclient.subscribe(topicTitle);
				}
			}
	
			WakeLockManager.release();
		}
	}
	
	//**** BACKGROUND DATA STATUS CHANGE RECEIVER ****//
	
	/**
	 * Detects changes in background data permission from the user
	 * @author TheHive
	 *
	 */
	private class BackgroundDataAvailabilityReceiver extends BroadcastReceiver
	{
		@Override
		public void onReceive(Context context, Intent intent) {
			
			WakeLockManager.acquire(getApplicationContext(),wakelockTag);
			
			if(connectivityDetector.isBackgroundDataUsageAllowed())
			{
				start();
			}
			else
			{
				//Notify user
				noticemanager.createNotification("Service not connect", "Background data is disabled.", NoticeManager.NOTIFICATION_AUTOCANCEL);
				
				subscriberclient.disconnectFromMqttBroker(); // disconnect from broker as data usage is not available
			}
			
			WakeLockManager.release();
		}
	}

	//************MqttSimpleCallback***************//
	
	/**
	 * This is an implemented method from the mqttclient to handle lost connection
	 */
	@Override
	public void connectionLost() throws Exception {
		
		WakeLockManager.acquire(getApplicationContext(),wakelockTag);
		
		if(connectivityDetector.isConnectedToNetworkOnline())
		{
			//notify user
			noticemanager.createNotification("Reconnecting", "Connection lost, trying to reconnect..", NoticeManager.NOTIFICATION_AUTOCANCEL);
			
			if(subscriberclient.connectToMqttBroker())
			{
				subscriberclient.subscribe(topicTitle);
			}
		}
		else
		{
			//Notify user
			noticemanager.createNotification("Connection lost", "Service have lost connection to the broker.", NoticeManager.NOTIFICATION_AUTOCANCEL);
		}
		
		
		WakeLockManager.release();
	}

	/**
	 * This is an implemented method from the mqttclient to handle incoming messages(non-Javadoc)
	 * from the publisher
	 * @see com.ibm.mqtt.MqttSimpleCallback#publishArrived(java.lang.String, byte[], int, boolean)
	 */
	@Override
	public void publishArrived(String topic, byte[] payload, int qos, boolean retained)
			throws Exception {
		
		
		WakeLockManager.acquire(getApplicationContext(),"VizLabMqttDataPush wakeup");
		
		initTextToSpeech();
		
		String msgfrBroker = new String(payload);
		Log.i("datapushmsgreceived","message received "+ msgfrBroker);
		//alternative store message in a temp file
		
		noticemanager = new NoticeManager(getApplicationContext(), NoticeManager.PREDEFINED_NOTIFICATION_ID1, NoticeManager.PREDEFINED_NOTIFICATION_TAG1, R.drawable.vizlabnotificationicon);
		
		noticemanager.createNotification(ViewResultDataActivity.class, "New Data Available", "A new data is now available! Click the notification to view the data.");
		
		speakAlert("A new data is now available");
		
		WakeLockManager.release();
	}
	
	
	/**
	 * A collection of status that the service can have
	 */
	public void populateStatusMap()
	{
		statusMap.put("INITIALISING", "Service..initialising...");
		statusMap.put("RUNNING", "Service is now running..");
		statusMap.put("WAITING_BROKER_CONNECTION", "Service, connecting to message broker...");
		statusMap.put("CONNECTED_BROKER", "Service is now connected to the broker.");
		statusMap.put("NO_INTERNET_AVAILABLE", "No current Internet connection available");
		statusMap.put("DISCONNECTED_BROKER", "Service has disconnected from the broker.");
		statusMap.put("DATA_USAGE_NOTALLOWED", "Service not connected. Background data is disabled.");
	}
}
